Implement VCPUOP_register_vcpu_info
authorJeremy Fitzhardinge <jeremy@xensource.com>
Thu, 24 May 2007 09:47:27 +0000 (10:47 +0100)
committerJeremy Fitzhardinge <jeremy@xensource.com>
Thu, 24 May 2007 09:47:27 +0000 (10:47 +0100)
This change implements the VCPUOP_register_vcpu_info vcpu_op.  This
allows a guest to choose where each VCPU's vcpu_info structure is
placed within its address space, allowing it to put it somewhere which
is easily accessible via some per-cpu data access mechanism.

When changing the mapping of the vcpu info, there's no obvious way to
prevent the other vcpus from getting a stale pointer of the vcpu_info,
which could result in them accessing bad memory (stale pointers to the
shared_info page are not a problem, because its always valid).  To
avoid this, we prevent guests from changing the vcpu_info location
more than once, since there's no obvious need to allow them to do this
at this point.

(If we really want to allow guests to update the vcpu_info location
more than once, then some sort of RCU wait between updating the
pointer and performing the unmap may be the way to do it.)

Signed-off-by: Jeremy Fitzhardinge <jeremy@xensource.com>
xen/arch/x86/domain.c
xen/common/domain.c
xen/include/public/vcpu.h
xen/include/xen/sched.h

index 121c484d47b7af55370f944b181944dd848e6f91..e413b0249c4c34fbebd60d8c98e30e67d7879bae 100644 (file)
@@ -28,6 +28,7 @@
 #include <xen/event.h>
 #include <xen/console.h>
 #include <xen/percpu.h>
+#include <xen/compat.h>
 #include <asm/regs.h>
 #include <asm/mc146818rtc.h>
 #include <asm/system.h>
@@ -49,6 +50,8 @@
 DEFINE_PER_CPU(struct vcpu *, curr_vcpu);
 DEFINE_PER_CPU(__u64, efer);
 
+static void unmap_vcpu_info(struct vcpu *v);
+
 static void paravirt_ctxt_switch_from(struct vcpu *v);
 static void paravirt_ctxt_switch_to(struct vcpu *v);
 
@@ -728,11 +731,94 @@ int arch_set_info_guest(
 
 int arch_vcpu_reset(struct vcpu *v)
 {
+    unmap_vcpu_info(v);
     destroy_gdt(v);
     vcpu_destroy_pagetables(v);
     return 0;
 }
 
+/* 
+ * Unmap the vcpu info page if the guest decided to place it somewhere
+ * else.  This is only used from arch_vcpu_reset, so there's no need
+ * to do anything clever.
+ */
+static void
+unmap_vcpu_info(struct vcpu *v)
+{
+    struct domain *d = v->domain;
+    unsigned long mfn;
+
+    if ( v->vcpu_info_mfn == INVALID_MFN )
+        return;
+
+    mfn = v->vcpu_info_mfn;
+    unmap_domain_page_global( v->vcpu_info );
+
+    v->vcpu_info = shared_info_addr(d, vcpu_info[v->vcpu_id]);
+    v->vcpu_info_mfn = INVALID_MFN;
+
+    put_page_and_type(mfn_to_page(mfn));
+}
+
+/* 
+ * Map a guest page in and point the vcpu_info pointer at it.  This
+ * makes sure that the vcpu_info is always pointing at a valid piece
+ * of memory, and it sets a pending event to make sure that a pending
+ * event doesn't get missed.
+ */
+static int
+map_vcpu_info(struct vcpu *v, unsigned long mfn, unsigned offset)
+{
+    struct domain *d = v->domain;
+    void *mapping;
+    vcpu_info_t *new_info;
+    int i;
+
+    if ( offset > (PAGE_SIZE - sizeof(vcpu_info_t)) )
+        return -EINVAL;
+
+    if ( mfn == INVALID_MFN ||
+         v->vcpu_info_mfn != INVALID_MFN )
+        return -EINVAL;
+
+    mfn = gmfn_to_mfn(d, mfn);
+    if ( !mfn_valid(mfn) ||
+         !get_page_and_type(mfn_to_page(mfn), d, PGT_writable_page) )
+        return -EINVAL;
+
+    mapping = map_domain_page_global(mfn);
+    if ( mapping == NULL )
+    {
+        put_page_and_type(mfn_to_page(mfn));
+        return -ENOMEM;
+    }
+
+    new_info = (vcpu_info_t *)(mapping + offset);
+
+    memcpy(new_info, v->vcpu_info, sizeof(*new_info));
+
+    v->vcpu_info = new_info;
+    v->vcpu_info_mfn = mfn;
+
+    /* make sure all the pointers are uptodate before setting pending */
+    wmb();
+
+    /* Mark everything as being pending just to make sure nothing gets
+       lost.  The domain will get a spurious event, but it can
+       cope. */
+    vcpu_info(v, evtchn_upcall_pending) = 1;
+    for ( i = 0; i < BITS_PER_GUEST_LONG(d); i++ )
+        set_bit(i, vcpu_info_addr(v, evtchn_pending_sel));
+
+    /* Only bother to update time for the current vcpu.  If we're
+     * operating on another vcpu, then it had better not be running at
+     * the time. */
+    if ( v == current )
+         update_vcpu_system_time(v);
+
+    return 0;
+}
+
 long
 arch_do_vcpu_op(
     int cmd, struct vcpu *v, XEN_GUEST_HANDLE(void) arg)
@@ -769,6 +855,22 @@ arch_do_vcpu_op(
         break;
     }
 
+    case VCPUOP_register_vcpu_info:
+    {
+        struct domain *d = v->domain;
+        struct vcpu_register_vcpu_info info;
+
+        rc = -EFAULT;
+        if ( copy_from_guest(&info, arg, 1) )
+            break;
+
+        LOCK_BIGLOCK(d);
+        rc = map_vcpu_info(v, info.mfn, info.offset);
+        UNLOCK_BIGLOCK(d);
+
+        break;
+    }
+
     default:
         rc = -ENOSYS;
         break;
index 5d13042f73b1e2071559e397b310c4a498d9b624..031ee3fc8c5a0a162e6106ada46f4bbaded13404 100644 (file)
@@ -136,6 +136,7 @@ struct vcpu *alloc_vcpu(
 
     v->domain = d;
     v->vcpu_id = vcpu_id;
+    v->vcpu_info_mfn = INVALID_MFN;
 
     v->runstate.state = is_idle_vcpu(v) ? RUNSTATE_running : RUNSTATE_offline;
     v->runstate.state_entry_time = NOW();
index 845f7e2c0566784fade041255274f5a3c412c9c7..6c6558e3caf333618d0a5685a7d03ee4401e9026 100644 (file)
@@ -168,8 +168,7 @@ DEFINE_XEN_GUEST_HANDLE(vcpu_set_singleshot_timer_t);
  * The pointer need not be page aligned, but the structure must not
  * cross a page boundary.
  *
- * If the specified mfn is INVALID_MFN, then it reverts to using the
- * vcpu_info structure in the shared_info page.
+ * This may be called only once per vcpu.
  */
 #define VCPUOP_register_vcpu_info   10  /* arg == struct vcpu_info */
 struct vcpu_register_vcpu_info {
index 703b3399182ed1cd4f212bde4ceab5727e2101cc..5ce2f5db7ad32ab571c3c0372817a05b03c0529e 100644 (file)
@@ -75,6 +75,7 @@ struct vcpu
     int              processor;
 
     vcpu_info_t     *vcpu_info;
+    unsigned long    vcpu_info_mfn;
 
     struct domain   *domain;